import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

public class FileOperation {

	/** Pre-initialized instance */
	private static FileOperation INSTANCE = new FileOperation();
	
	// Set of strings for the file filter
	private static Set<String> exts = new HashSet<String>();

	/** Private constructor */
	private FileOperation() {
	}

	/** Access point for the singleton instance */
	public static FileOperation getInstance() {
		return INSTANCE;
	}

	/* Serialize a file and create a new file with "-serialized.ser" */
	public static File serializeFile(FileObject fo) {

		String newFileName = fo.dir + "/" + removeExt(fo.fileName)
				+ "-ser.ser";

		try {
			// use buffering
			OutputStream file = new FileOutputStream(newFileName);
			OutputStream buffer = new BufferedOutputStream(file);
			ObjectOutput output = new ObjectOutputStream(buffer);
			try {
				output.writeObject(fo);
			} finally {
				output.close();
			}
		} catch (IOException ex) {
			System.out.println("Cannot perform output." + ex);
		}

		return new File(newFileName);
	}

	/* Unserialize a File Object and re-create the file from it */
	public static FileObject unserializeFile(File file) {

		FileObject recoveredFile = null;

		try {

			// use buffering
			InputStream fileStream = new FileInputStream(file.getAbsolutePath());
			InputStream buffer = new BufferedInputStream(fileStream);
			ObjectInput input = new ObjectInputStream(buffer);
			try {

				// Deserialize
				recoveredFile = (FileObject) input.readObject();
				File createDirs = new File(recoveredFile.dir);
				createDirs.mkdirs();
				// Write file
				recoveredFile.writeFile(recoveredFile.dir + "/"
						+ "OK-"+recoveredFile.fileName);
			} finally {
				input.close();
			}
		} catch (ClassNotFoundException ex) {
			System.out.println("Cannot perform input. Class not found." + ex);
		} catch (IOException ex) {
			System.out.println("Cannot perform input." + ex);
		}

		file.delete();

		return recoveredFile;
	}

	/* Encrypt a file and create a new file with extension .crp */
	public static File encrytpFile(File file, String key) {

		String path = getPathOnly(file.getAbsolutePath());
		String fileName = path + "/" + removeExt(file.getName()) + ".crp";

		try {

			FileInputStream fis = new FileInputStream(file.getAbsolutePath());
			FileOutputStream fos = new FileOutputStream(fileName);
			encrypt(key, fis, fos);

		} catch (Throwable e) {
			e.printStackTrace();
		}

		file.delete();

		return new File(fileName);
	}

	/* Decrypt a file and create a new file with extension .ser */
	public static File decryptFile(File file, String key) {

		String path = getPathOnly(file.getAbsolutePath());
		String fileName = path + "/" + removeExt(file.getName()) + ".ser";

		try {

			FileInputStream fis2 = new FileInputStream(file.getAbsolutePath());
			FileOutputStream fos2 = new FileOutputStream(fileName);
			decrypt(key, fis2, fos2);

		} catch (Throwable e) {
			e.printStackTrace();
		}

		return new File(fileName);
	}

	private static void encrypt(String key, InputStream is, OutputStream os)
			throws Throwable {
		encryptOrDecrypt(key, Cipher.ENCRYPT_MODE, is, os);
	}

	private static void decrypt(String key, InputStream is, OutputStream os)
			throws Throwable {
		encryptOrDecrypt(key, Cipher.DECRYPT_MODE, is, os);
	}

	private static void encryptOrDecrypt(String key, int mode, InputStream is,
			OutputStream os) throws Throwable {

		DESKeySpec dks = new DESKeySpec(key.getBytes());
		SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
		SecretKey desKey = skf.generateSecret(dks);
		Cipher cipher = Cipher.getInstance("DES"); // DES/ECB/PKCS5Padding for
													// SunJCE

		if (mode == Cipher.ENCRYPT_MODE) {
			cipher.init(Cipher.ENCRYPT_MODE, desKey);
			CipherInputStream cis = new CipherInputStream(is, cipher);
			doCopy(cis, os);
		} else if (mode == Cipher.DECRYPT_MODE) {
			cipher.init(Cipher.DECRYPT_MODE, desKey);
			CipherOutputStream cos = new CipherOutputStream(os, cipher);
			doCopy(is, cos);
		}
	}

	private static void doCopy(InputStream is, OutputStream os)
			throws IOException {
		byte[] bytes = new byte[64];
		int numBytes;
		while ((numBytes = is.read(bytes)) != -1) {
			os.write(bytes, 0, numBytes);
		}
		os.flush();
		os.close();
		is.close();
	}

	public static String getPathOnly(String absolutePath) {
		return absolutePath.substring(0,
				absolutePath.lastIndexOf(File.separator));
	}

	public static String removeExt(String fileName) {

		String name = fileName;
		int pos = name.lastIndexOf(".");
		if (pos > 0) {
			name = name.substring(0, pos);
		}

		return name;
	}
	
	
	/* Creates the extension filter */
	public static FilenameFilter withExtension(String ... extensions) {
		checkNotNull(extensions);
		
		for (String ext : extensions) {
            exts.add("." + ext.toLowerCase().trim());
        }
		
		return new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				
				final Iterator<String> extList = exts.iterator();
		        while (extList.hasNext()) {
		            if (name.toLowerCase().endsWith(extList.next())) {
		                return true;
		            }
		        }
		        return false;
			}
		};
	}

	/* List a dir with the good filter */
	public static List<File> listDir(File dir, FilenameFilter filter) {

		checkNotNull(dir);
		isDir(dir);
		List<File> files = new ArrayList<File>();
		listDir(dir, filter, files);
		return files;
	}

	/* Recursive function to list file inside a dir with a given filter */
	private static void listDir(File dir, FilenameFilter filter,
			List<File> files) {

		assert dir.isDirectory();
		File[] everyFiles = dir.listFiles();
		if (everyFiles != null) {
			for (File file : dir.listFiles()) {
				if (file.isDirectory()) {
					listDir(file, filter, files);
				} else if (filter.accept(dir, file.getName())) {
					files.add(file);
				}
			}
		}
	}

	/* Check if null and raise an exception if so */
	private static void checkNotNull(Object object) {
		if (object == null) {
			throw new NullPointerException();
		}
	}

	/* Check if the file is a dir */
	private static void isDir(File file) {
		if (!file.isDirectory()) {
			throw new IllegalArgumentException();
		}
	}
}
